﻿using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Prng;

namespace MyTRCP.Common.MyProtocol
{
    public class MyProtocolCryptoHelper
    {
        public static bool CheckPublicKeyIsInTrustList(ECPublicKeyParameters pubKey, List<ECPublicKeyParameters> keyList)
        {
            foreach (ECPublicKeyParameters key in keyList)
            {
                if (pubKey.Equals(key))
                {
                    return true;
                }
            }
            return false;
        }
        public static byte[] CreateChallenge(byte[] challengeRandom, AsymmetricKeyParameter pubKey)
        {
            return SM2Encrypt(challengeRandom, (ECPublicKeyParameters)pubKey);
        }
        public static byte[] ChallengeResponse(byte[] challenge, AsymmetricKeyParameter priKey)
        {
            return SM3Hash(SM2Decrypt(challenge, (ECPrivateKeyParameters)priKey));
        }
        public static bool CheckChallengeResponseCorrect(byte[] response, byte[] challengeRandom)
        {
            return SM3Hash(challengeRandom).SequenceEqual(response);
        }
        public static byte[] SM2Decrypt(byte[] data, ECPrivateKeyParameters priKeyParam)
        {
            SM2Engine cipher = new SM2Engine();
            cipher.Init(false, priKeyParam);
            return cipher.ProcessBlock(data);
        }
        public static byte[] SM2Encrypt(byte[] data, ECPublicKeyParameters pubKeyParam)
        {
            SM2Engine cipher = new SM2Engine();
            cipher.Init(true, pubKeyParam);
            return cipher.ProcessBlock(data);
        }
        public static byte[] SM3Hash(byte[] data)
        {
            SM3Digest sm3 = new SM3Digest();
            sm3.BlockUpdate(data);
            byte[] hashBytes = new byte[sm3.GetDigestSize()];
            sm3.DoFinal(hashBytes, 0);
            return hashBytes;
        }
        public static ParametersWithIV GenerateSM4KeyFromBytes(byte[] bytes)
        {
            ReadOnlySpan<byte> key = bytes;
            ReadOnlySpan<byte> iv = SM3Hash(bytes);
            return new ParametersWithIV(new KeyParameter(key[..16]), iv[..16]);
        }
        public static byte[] SM4Decrypt(byte[] data, ParametersWithIV keyParam)
        {
            IBufferedCipher inCipher = CipherUtilities.GetCipher("SM4/CBC/PKCS7Padding");
            inCipher.Init(false, keyParam);
            return inCipher.DoFinal(data);
        }
        public static byte[] SM4Encrypt(byte[] data, ParametersWithIV keyParam)
        {
            IBufferedCipher inCipher = CipherUtilities.GetCipher("SM4/CBC/PKCS7Padding");
            inCipher.Init(true, keyParam);
            return inCipher.DoFinal(data);
        }
        public static ECDomainParameters GetMyECDomainParameters()
        {
            X9ECParameters ecParams = ECNamedCurveTable.GetByName("K-283");
            return new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
        }
        public static byte[] ECPublicKeyXToBytes(AsymmetricKeyParameter publicKey)
        {
            return ((ECPublicKeyParameters)publicKey).Q.XCoord.GetEncoded();
        }
        public static byte[] ECPublicKeyYToBytes(AsymmetricKeyParameter publicKey)
        {
            return ((ECPublicKeyParameters)publicKey).Q.YCoord.GetEncoded();
        }
        public static byte[] ECPrivateKeyToBytes(AsymmetricKeyParameter privateKey)
        {
            return ((ECPrivateKeyParameters)privateKey).D.ToByteArrayUnsigned();
        }
        public static ECPrivateKeyParameters ECPrivateKeyFromMyBytes(byte[] bytes)
        {
            return new ECPrivateKeyParameters(new BigInteger(1, bytes), GetMyECDomainParameters());
        }
        public static ECPublicKeyParameters ECPublicKeyFromMyBytes(byte[] pubKeyX, byte[] pubKeyY)
        {
            ECDomainParameters dp = GetMyECDomainParameters();
            return new ECPublicKeyParameters(dp.Curve.CreatePoint(new BigInteger(1, pubKeyX), new BigInteger(1, pubKeyY)), dp);
        }
        public static readonly SecureRandom SecRandom = new SecureRandom();
        public static AsymmetricCipherKeyPair GenerateMyECDHKeyPair()
        {
            return GenerateECDHKeyPair(GetMyECDomainParameters(), SecRandom);
        }
        public static AsymmetricCipherKeyPair GenerateFixedECDHKeyPair(int rndSeed)
        {
            return GenerateECDHKeyPair(GetMyECDomainParameters(), new SecureRandom(new FixedRandomGenerator(rndSeed)));
        }
        public static AsymmetricCipherKeyPair GenerateECDHKeyPair(ECDomainParameters ecParams, SecureRandom rnd)
        {
            ECKeyGenerationParameters ecKeyGenParams = new ECKeyGenerationParameters(ecParams, rnd);
            ECKeyPairGenerator ecKeyPairGen = new ECKeyPairGenerator();
            ecKeyPairGen.Init(ecKeyGenParams);
            AsymmetricCipherKeyPair ecKeyPair = ecKeyPairGen.GenerateKeyPair();
            return ecKeyPair;
        }
        public static BigInteger GetECAgreementResult(ECPrivateKeyParameters aPrivateKey, ECPublicKeyParameters bPublicKey)
        {
            ECDHCBasicAgreement keyAgreement = new ECDHCBasicAgreement();
            keyAgreement.Init(aPrivateKey);
            BigInteger secret = keyAgreement.CalculateAgreement(bPublicKey);
            return secret;
        }
        private class FixedRandomGenerator : IRandomGenerator
        {
            private int _fixedRandomSeed;
            private Random _fixedRandom;
            public FixedRandomGenerator(int seed)
            {
                _fixedRandomSeed = seed;
                _fixedRandom = new Random(_fixedRandomSeed);
            }

            public void AddSeedMaterial(byte[] seed) { }

            public void AddSeedMaterial(long seed)
            {
                _fixedRandomSeed = (int)(seed);
                _fixedRandom = new Random(_fixedRandomSeed);
            }

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
            public void AddSeedMaterial(ReadOnlySpan<byte> seed) { }
#endif

            public void NextBytes(byte[] bytes)
            {
                _fixedRandom.NextBytes(bytes);
            }

            public void NextBytes(byte[] bytes, int start, int len)
            {
                byte[] result = new byte[len];
                _fixedRandom.NextBytes(result);
                Array.Copy(bytes, start, result, 0, len);
            }

            public byte[] NextBytes(int len)
            {
                byte[] result = new byte[len];
                _fixedRandom.NextBytes(result);
                return result;
            }

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
            public void NextBytes(Span<byte> bytes)
            {
                byte[] result = bytes.ToArray();
                this.NextBytes(result);
                result.AsSpan(0, result.Length).CopyTo(bytes);
            }
#endif

        }

    }
}
